package user

import (
	"gitee.com/mootok/mootok/src/constant/strings"
	redis2 "gitee.com/mootok/mootok/src/constant/strings/redis"
	"gitee.com/mootok/mootok/src/models"
	"gitee.com/mootok/mootok/src/services/feed"
	"gitee.com/mootok/mootok/src/services/sms"
	user2 "gitee.com/mootok/mootok/src/services/user"
	"gitee.com/mootok/mootok/src/storage/database"
	"gitee.com/mootok/mootok/src/storage/file"
	"gitee.com/mootok/mootok/src/utils/logging"
	"gitee.com/mootok/mootok/src/web/api"
	"gitee.com/mootok/mootok/src/web/common/response"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	redis3 "github.com/redis/go-redis/v9"
	"github.com/sirupsen/logrus"
	"net/http"
	"time"
)

type ApiUserImpl struct {
}

var Service user2.Service
var SmsService sms.Service
var FeedService feed.Service
var LinkExpirationTime = int64((48 * time.Hour).Seconds())

func (*ApiUserImpl) GetLoginUserInfo(c *gin.Context) {

	userId, _ := c.Get(strings.UserLoginContext)

	logger := logging.LogService("User.LoginUserInfo.Get").WithFields(logrus.Fields{
		"user_id": userId,
	})
	//find user from db
	user := &models.User{}
	if err := database.Client.First(&user, userId).Error; err != nil {
		logger.Errorf("user select from db failed %v ", err)
		response.FailWithMsg(c, http.StatusInternalServerError, strings.DataBaseSelectErrorCode, strings.InternalServiceError)
		return
	}

	user.AvatarUrl = file.Instance.GetLink(user.AvatarUrl, LinkExpirationTime, nil)

	response.SuccessWithDetail(c, strings.SuccessCode, strings.Success, &api.UserInfo{
		Id:        &user.ID,
		AvatarUrl: &user.AvatarUrl,
		Nickname:  &user.NickName,
		Sex:       &user.Sex,
	})

}

func (*ApiUserImpl) Logout(c *gin.Context) {
	cookie, err := c.Cookie(strings.UserLoginCookie)
	logger := logging.LogService("User.LoginUserInfo.Get").WithFields(logrus.Fields{
		"cookie": cookie,
	})
	if err != nil {
		response.FailWithMsg(c, http.StatusUnauthorized, strings.UserNotLoginErrorCode, strings.UserNotLoginError)
		return
	}
	err = Service.RemoveUserIdByToken(c, cookie)
	//token has expired or deleted
	if err != nil {
		if err == redis3.Nil {
			logger.Errorf("redis del val failed %v", err)
			response.FailWithMsg(c, http.StatusInternalServerError, strings.RedisDelValueErrorCode, strings.InternalServiceError)
		} else {
			logger.Errorf("redis operation failed %v", err)
			response.FailWithMsg(c, http.StatusInternalServerError, strings.RedisOperationErrorCode, strings.InternalServiceError)
		}
		return
	}

	response.Success(c, strings.SuccessCode)
}

func (*ApiUserImpl) RegisterAndLogin(c *gin.Context) {
	//TODO rate limit
	userRegisterAndLoginReq := api.UserRegisterAndLoginReq{}
	err := c.ShouldBindJSON(&userRegisterAndLoginReq)

	if err != nil {
		response.FailWithMsg(c, http.StatusBadRequest, strings.ParamsErrorCode, strings.ParamsError)
		return
	}

	//get code from redis
	code := userRegisterAndLoginReq.Code
	phone := userRegisterAndLoginReq.Phone

	logger := logging.LogService("User.Auth").WithFields(logrus.Fields{
		"phone": phone,
		"code":  code,
	})

	codeCache, err := SmsService.GetAuthCode(c, phone)

	//key has expired
	if err != nil {
		if err == redis3.Nil {
			logger.Error("code expired.")
			response.SuccessWithMsg(c, strings.UserLoginCacheCodeValidFailedCode, strings.UserLoginCacheCodeValidFailed)
		} else {
			logger.Error("redis operation failed:%v", err)
			response.FailWithMsg(c, http.StatusInternalServerError, strings.RedisOperationErrorCode, strings.InternalServiceError)
		}
		return
	}

	if code != codeCache {
		logger.Error("code does not match.")
		response.SuccessWithMsg(c, strings.UserLoginCacheCodeValidFailedCode, strings.UserLoginCacheCodeValidFailed)
		return
	}

	//find user by phone
	user := models.User{}

	result := database.Client.Where("phone = ?", phone).Find(&user)
	//register
	err = result.Error
	if err != nil {
		logger.Errorf("user query from db failed:%v", err)
		response.FailWithMsg(c, http.StatusInternalServerError, strings.DataBaseSelectErrorCode, strings.InternalServiceError)
		return
	}

	//注册用户
	if result.RowsAffected == 0 {
		//register
		user.NickName = genRandomNickName()
		user.Phone = phone
		//todo 默认头像？
		user.AvatarUrl = "U1%40%25M2%60IO%251DXC6%5BUEH1_06.jpg"
		//register user
		if err = database.Client.Create(&user).Error; err != nil {
			logger.Errorf("user creation to db failed:%v", err)
			response.FailWithMsg(c, http.StatusInternalServerError, strings.DataBaseInsertErrorCode, strings.InternalServiceError)
			return
		}
		logger.Errorf("user creation success.")
	}

	//login
	uuid, err := uuid.NewUUID()
	if err != nil {
		logger.Errorf("unable generate uuid:%v", err)
		response.FailWithMsg(c, http.StatusInternalServerError, strings.UUIDGenerateErrorCode, strings.InternalServiceError)
		return
	}

	//return token
	token := uuid.String()
	err = Service.SetUserIdByToken(c, token, user.ID)
	//redis error
	if err != nil {
		logger.Errorf("unable generate uuid:%v", err)
		response.FailWithMsg(c, http.StatusInternalServerError, strings.RedisSetValueErrorCode, strings.InternalServiceError)
		return
	}

	//set cookie
	c.SetCookie(strings.UserLoginCookie, token, redis2.UserLoginTokenExpireTime, "", "", false, true)
	if err = FeedService.PreLoadFeed(c, user.ID); err != nil {
		logger.Errorf("LoadUerVideoFeedAfterLogin faile:%v", err)
		response.FailWithMsg(c, http.StatusInternalServerError, strings.RedisOperationErrorCode, strings.InternalServiceError)
		return
	}
	response.Success(c, strings.SuccessCode)
	logger.Errorf("auth success.")
}

func (*ApiUserImpl) UpdateLoginUserInfo(c *gin.Context, id int) {
	userId, _ := c.Get(strings.UserLoginContext)
	userInfo := api.UserInfo{}
	err := c.ShouldBindJSON(&userInfo)
	//400
	if err != nil {
		response.FailWithMsg(c, http.StatusBadRequest, strings.ParamsErrorCode, strings.ParamsError)
		return
	}

	logger := logging.LogService("User.LoginUserInfo.Get").WithFields(logrus.Fields{
		"user_id":    userId,
		"avatar_url": userInfo.AvatarUrl,
		"sex":        userInfo.Sex,
		"nick_name":  userInfo.Nickname,
	})

	//403
	if id != userId {
		logger.Errorf("fobidden operation for user")
		response.FailWithMsg(c, http.StatusForbidden, strings.UserNotAuthorErrorCode, strings.UserNotAuthorError)
		return
	}

	//update
	if err := database.Client.Model(&models.User{}).Where("id = ?", id).
		Updates(map[string]interface{}{"sex": *userInfo.Sex, "nick_name": *userInfo.Nickname, "avatar_url": *userInfo.AvatarUrl}).Error; err != nil {
		response.FailWithMsg(c, http.StatusInternalServerError, strings.DataBaseInsertErrorCode, strings.InternalServiceError)
		return
	}

	response.Success(c, strings.SuccessCode)

}
